#include "FileRecord.h"
#include <algorithm>

#ifndef ZERO
	#define ZERO 0
#endif

#ifndef UNO
	#define UNO 1
#endif

#define SIZE_OVERHEAD 128
#define FIRST_RECORD 128
#define SIZE_OVERHEAD_RECORD ((2 * sizeof(unsigned int)) + sizeof(char))
#define OCUPADE_RECORD 'O'
#define FREE_RECORD 'F'
#define ERASE_RECORD 'E'

FileRecord::FileRecord()
	:name(NULL),
	recordPointer(ZERO),
	firstRecordFree(ZERO)
{
}

FileRecord::~FileRecord()
{
	if(name != NULL)
		delete []name;
	freeRecords.clear();
}

void FileRecord::loadFreeRecords(long int firstRecordFree)
{
	unsigned int sizeRecord = ZERO;
	long int sizeFreeRecord = ZERO;
	char state = FREE_RECORD;
	File* file = NULL;

	if(name != NULL)
	{
		file = new File();
		file->open(name);
		// Obtengo el overhead del registro
		this->getOverhead(firstRecordFree, sizeRecord, sizeFreeRecord, &state);

		while(state == FREE_RECORD && sizeFreeRecord != ZERO)
		{
			freeRecords.push_back(sizeFreeRecord);
			this->getOverhead(sizeFreeRecord, sizeRecord, sizeFreeRecord, &state);
		}
		delete file;
		
	}
}

void FileRecord::getFreeRecord(unsigned int size, long int &offset)
{
	unsigned int sizeRecord = ZERO;
	long int sizeFreeRecord = ZERO, previous = ZERO, next = ZERO;
	char state = ' ';
	bool find = false;
	File* file = NULL;

	if(name != NULL)
	{
		file = new File();
		file->open(name);
		// Obtengo el overhead del registro
		find = false;

		for(std::vector<long int>::iterator it = freeRecords.begin(); it != freeRecords.end() && !find; it++ )
		{
			offset = (*it);
			this->getOverhead(offset, sizeRecord, sizeFreeRecord, &state);
			if(sizeRecord > size)
			{
				find = true;
				if(it != freeRecords.end())
					next = *(it + sizeof(long int));
				else
					next = ZERO;
			}
			else
				previous = offset;
		}
		
		if(!find)
		{
			// Retorno el ultimo registro
			if(file != NULL)
				offset = file->getSize(name);
		}
		else
		{
			// Tengo que sacar el registro de la lista de registros libres
			// y encadenar los registros linderos
			std::vector<long int>::iterator itRemove = std::find(freeRecords.begin(), freeRecords.end(), offset);
			if(itRemove != freeRecords.end())
				freeRecords.erase(itRemove);

			if(file != NULL)
			{
				file->setCurrentPos(previous + sizeof(unsigned int));
				file->write((char*)&next, sizeof(long int));
			}
			
		
			
			
		}
		delete file;
		
	}	
}

void FileRecord::writeRecord(long int offset, unsigned int sizeFreeRecord, char* record, unsigned int size)
{
	File* file = NULL;
	char ocupade = OCUPADE_RECORD;

	if(name != NULL)
	{
		file = new File();
		file->open(name);
		file->setCurrentPos(offset);
		file->write((char*)&size, sizeof(unsigned int));
		file->write((char*)&ocupade, sizeof(char));
		file->write((char*)&sizeFreeRecord, sizeof(long int));
		file->write(record, size);
		delete file;
	}
}

void FileRecord::getOverhead(long int offset, unsigned int &sizeRecord, long int &sizeFreeRecord, char *state)
{
	File* file = NULL;
	char *overhead = NULL, *current = NULL;
	unsigned int sizeOverhead = sizeof(long int) + sizeof(unsigned int) + sizeof(char);

	if(name != NULL)
	{
		file = new File();
		file->open(name);
		overhead = new char[sizeOverhead];

		file->setCurrentPos(offset);
		file->read(overhead, sizeOverhead);

		current = overhead;

		sizeRecord = ((unsigned int*)current)[ZERO];
		current += sizeof(unsigned int);
		state[ZERO] = current[ZERO];
		current += sizeof(char);
		sizeFreeRecord = ((long int*)current)[ZERO];

		delete []overhead;
		delete file;
	}
}

void FileRecord::addFreeRecord(long int offset)
{
	freeRecords.push_back(offset);
}

unsigned int FileRecord::Create(const char* filename)
{
	File* file = NULL;
	char overhead[SIZE_OVERHEAD] = {ZERO};
	unsigned int sizeOverhead = SIZE_OVERHEAD;
	long int first = FIRST_RECORD;

	if(filename != NULL)
	{
		file = new File();

		file->open(filename);

		if(name != NULL)
			delete []name;
		name = new char[strlen(filename) + UNO];
		memcpy(name, filename, strlen(filename));
		name[strlen(filename)] = '\0';

		// Inicializo el primer registro libre en el overhead del archivo
		memcpy(overhead, (char*)&first, sizeof(long int));
		
		// Inicializo los atributos
		firstRecordFree = recordPointer = ZERO;

		// Escribo el overhead del archivo
		if(overhead != NULL)
			file->write(overhead, sizeOverhead);
	
		delete file;
	}
	else
		return FR_ERROR;
	return FR_OK;
}

unsigned int FileRecord::Open(const char* filename)
{
	char overhead[SIZE_OVERHEAD] = {ZERO};
	File* file = NULL;

	if(filename != NULL)
	{
		file = new File();

		file->open(filename);

		if(name != NULL)
			delete []name;
		name = new char[strlen(filename) + UNO];
		memcpy(name, filename, strlen(filename));
		name[strlen(filename)] = '\0';

		file->read(overhead, SIZE_OVERHEAD);

		// Obtengo el primer registro libre en el overhead del archivo
		memcpy((char*)&firstRecordFree, overhead, sizeof(long int));

		// Levanto todos los registros libres a memoria
		if(firstRecordFree != ZERO)
			this->loadFreeRecords(firstRecordFree);
		
		// Inicializo el record pointer en el primer registro del archivo
		recordPointer = FIRST_RECORD;
		
		delete file;
	}
	else
		return FR_ERROR;
	return FR_OK;
}

unsigned int FileRecord::Add(char* record, unsigned int size, long int &offset)
{
	long int sizeFreeRecord = ZERO;

	if(record != NULL)
	{
		// Primero obtengo un registro libre con suficiente
		// espacio para insertar el registro
		this->getFreeRecord(size, offset);

		if(name != NULL)
			this->writeRecord(offset, sizeFreeRecord, record, size);
		else
			return FR_ERROR;
	}
	else
		return FR_ERROR;
	
	return FR_OK;
}

unsigned int FileRecord::Update(long int offset, char* newRecord, unsigned int size)
{
	unsigned int sizeRecord = ZERO;
	long int sizeFreeRecord = ZERO;
	char state = ' ';
	char *record = NULL, *current = NULL;
	File* file = NULL;

	if(newRecord != NULL && name != NULL)
	{
		this->getOverhead(offset, sizeRecord, sizeFreeRecord, &state);

		if(state == OCUPADE_RECORD)
		{
			if(size <= (unsigned int)sizeFreeRecord)
			{
				file = new File();
				file->open(name);
				record = new char[size + SIZE_OVERHEAD_RECORD];
				current = record;
	
				((unsigned int*)current)[ZERO] = size;
				current += sizeof(unsigned int);
				((unsigned int*)current)[ZERO] = (sizeFreeRecord - size);
				current += sizeof(unsigned int);
				current[ZERO] = OCUPADE_RECORD;
				current += sizeof(char);
		
				memcpy(current, newRecord, size);

				if(file != NULL)
				{
					file->setCurrentPos(offset);
					file->write(record, size);
				}
		
				delete []record;
				delete file;
			}
			else
			{
				this->Remove(offset);
				this->Add(newRecord, size, offset);
			}
		}
		else
		{
			return FR_NO_EXISTE_REGISTRO;
		}
		
	}
	else
		return FR_ERROR;
	return FR_OK;
}

unsigned int FileRecord::Remove(long int offset)
{
	unsigned int sizeRecord = ZERO;
	long int sizeFreeRecord = ZERO;
	char state = ' ';
	File* file = NULL;

	this->getOverhead(offset, sizeRecord, sizeFreeRecord, &state);

	if(state != FREE_RECORD)
	{
		state = FREE_RECORD;
		if(name != NULL)
		{
			file = new File();
			file->open(name);
			file->setCurrentPos(offset + SIZE_OVERHEAD_RECORD - sizeof(char));
			file->write(&state, sizeof(char));
			delete file;
		}
		else
			return FR_ERROR;

		this->addFreeRecord(offset);
	}
	else
		return FR_NO_EXISTE_REGISTRO;

	return FR_OK;
}

unsigned int FileRecord::Get(long int offset, char* record, unsigned int &size)
{
	unsigned int sizeRecord = ZERO;
	long int sizeFreeRecord = ZERO;
	char state = ' ';
	File* file = NULL;

	if(record != NULL)
	{
		this->getOverhead(offset, sizeRecord, sizeFreeRecord, &state);

		if(state != FREE_RECORD)
		{
			if(name != NULL)
			{
				file = new File();
				file->open(name);
				file->setCurrentPos(offset + SIZE_OVERHEAD_RECORD);
				file->read(record, sizeRecord);
				size = sizeRecord;
				delete file;
			}
			else
				return FR_ERROR;
		}
		else
			return FR_NO_EXISTE_REGISTRO;
	}
	else
		return FR_ERROR;
	return FR_OK;
}

unsigned int FileRecord::Close()
{
	if(name != NULL)
		delete []name;
	name = NULL;
	recordPointer = ZERO;
	firstRecordFree = ZERO;
	freeRecords.clear();
	return FR_OK;
}

unsigned int FileRecord::Destroy(const char* filename)
{
	remove(filename);
	return FR_OK;
}

bool FileRecord::HasNext()
{
	bool retorno = false;
	File* file = new File();
	
	retorno = !(file->getSize(name) == recordPointer);
	
	delete file;
	
	return retorno;
}

void FileRecord::Next(long int &offset, char* record, unsigned int &size)
{
	unsigned int sizeRecord = ZERO;
	long int sizeFreeRecord = ZERO;
	char state = FREE_RECORD;
	
	if(this->Get(recordPointer, record, size) == FR_OK)
	{
		// Tengo que mover el record pointer al proximo registro
		this->getOverhead(recordPointer, sizeRecord, sizeFreeRecord, &state);
		offset = recordPointer;
		recordPointer += (SIZE_OVERHEAD_RECORD + sizeRecord + sizeFreeRecord);
		
	}
	else
		offset = -1;
}

